3
文章转发自专业的Laravel开发者社区,原始链接:https://learnku.com/laravel/t...

将用户与角色进行关联

这个包允许你在数据库中管理用户的权限和角色。

当你安装了扩展包之后你就可以这样做:

// 给用户添加一个权限
$user->givePermissionTo('edit articles');

// 通过角色添加权限。
$user->assignRole('writer');

// 给角色添加一个权限
$role->givePermissionTo('edit articles');

如果你给单个用户添加了多个守卫(guard),扩展包也可以处理的很好,每一个分配给用户的守卫都有它自己的权限和角色,阅读 using multiple guards章节可以看见更多的信息。

因为所有的权限将注册在Laravel's gate上,所以你可以调用 Laravel 默认的 'can' 方法来测试用户是否有权限:

$user->can('edit articles');

Spatie 是一个位于 Antwerp, Belgium的web设计机构。你可以在我们的官网找到所有的开源项目。

安装

Laravel

这个包可以在 Laravel 5.4或更高版本中使用,如果你使用的是旧版本的Laravel,可以切换到 这个包的 v1 分支 去使用。

你可以通过 composer 去安装这个包:

composer require spatie/laravel-permission

在 Laravel 5.5 中 service provider 会自动注册,旧版本的Laravel中你需要像以下这样自行添加到 config/app.php 中:

'providers' => [
    // ...
    Spatie\Permission\PermissionServiceProvider::class,
];

你可以使用以下命令发布 migration

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"

如果你在 User 模型中使用 UUIDs 或 GUIDs 你可以修改 create_permission_tables.php migration 并替换 $table->morphs('model')

$table->uuid('model_id');
$table->string('model_type');

migration发布后,您可以通过运行以下命令来创建角色和权限表:

php artisan migrate

你可以运行以下命令生成配置文件:

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

当发布了配置文件后,就可以看到 config/permission.php 中包括:

return [

    'models' => [

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * Eloquent model should be used to retrieve your permissions. Of course, it
         * is often just the "Permission" model but you may use whatever you like.
         *
         * The model you want to use as a Permission model needs to implement the
         * `Spatie\Permission\Contracts\Permission` contract.
         */

        'permission' => Spatie\Permission\Models\Permission::class,

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * Eloquent model should be used to retrieve your roles. Of course, it
         * is often just the "Role" model but you may use whatever you like.
         *
         * The model you want to use as a Role model needs to implement the
         * `Spatie\Permission\Contracts\Role` contract.
         */

        'role' => Spatie\Permission\Models\Role::class,

    ],

    'table_names' => [

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your roles. We have chosen a basic
         * default value but you may easily change it to any table you like.
         */

        'roles' => 'roles',

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your permissions. We have chosen a basic
         * default value but you may easily change it to any table you like.
         */

        'permissions' => 'permissions',

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your models permissions. We have chosen a
         * basic default value but you may easily change it to any table you like.
         */

        'model_has_permissions' => 'model_has_permissions',

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your models roles. We have chosen a
         * basic default value but you may easily change it to any table you like.
         */

        'model_has_roles' => 'model_has_roles',

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your roles permissions. We have chosen a
         * basic default value but you may easily change it to any table you like.
         */

        'role_has_permissions' => 'role_has_permissions',
    ],

    /*
     * By default all permissions will be cached for 24 hours unless a permission or
     * role is updated. Then the cache will be flushed immediately.
     */

    'cache_expiration_time' => 60 * 24,
    
    /*
     * When set to true, the required permission/role names are added to the exception
     * message. This could be considered an information leak in some contexts, so
     * the default setting is false here for optimum safety.
     */

    'display_permission_in_exception' => false,
];

Lumen

通过 Composer 安装:

composer require spatie/laravel-permission

复制必须的文件:

cp vendor/spatie/laravel-permission/config/permission.php config/permission.php
cp vendor/spatie/laravel-permission/database/migrations/create_permission_tables.php.stub database/migrations/2018_01_01_000000_create_permission_tables.php

另外一个配置文件 config/auth.php, 你可以从 Laravel 仓库获取, 或者直接执行下面的命令获取:

curl -Ls https://raw.githubusercontent.com/laravel/lumen-framework/5.5/config/auth.php -o config/auth.php

现在,执行迁移:

php artisan migrate

接下来, 在 bootstrap/app.php 中, 注册中间件:

$app->routeMiddleware([
    'auth'       => App\Http\Middleware\Authenticate::class,
    'permission' => Spatie\Permission\Middlewares\PermissionMiddleware::class,
    'role'       => Spatie\Permission\Middlewares\RoleMiddleware::class,
]);

同样的 , 注册配置和服务提供者:

$app->configure('permission');
$app->register(Spatie\Permission\PermissionServiceProvider::class);

使用

首先,添加 Spatie\Permission\Traits\HasRoles trait 到 User 模型:

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    // ...
}
  • 请注意,如果你需要在另一个模型,例如 Page 中添加 HasRoles trait 你还需要添加 protected $guard_name = 'web'; 到这个模型中,否则会报错。
use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasRoles;

class Page extends Model
{
   use HasRoles;

   protected $guard_name = 'web'; // or whatever guard you want to use

   // ...
}

这个包允许用户与权限和角色相关联。每个角色都与多个权限相关联。
RolePermission 都是 Eloquent 模型,它们创建的时候需要传入 name 这个参数,就像下面这样:

use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

$role = Role::create(['name' => 'writer']);
$permission = Permission::create(['name' => 'edit articles']);

可以使用下面其中一个方法将权限分配给角色:

$role->givePermissionTo($permission);
$permission->assignRole($role);

可以使用下面其中一种方法将多个权限同步赋予到一个角色:

$role->syncPermissions($permissions);
$permission->syncRoles($roles);

可以使用以下其中一种方法通过角色去删除权限:

$role->revokePermissionTo($permission);
$permission->removeRole($role);

如果你使用多守卫的话, guard_name 必须要设置,在 使用多守卫 段落中有提及到。

HasRoles trait 具有 Eloquent 模型关系功能,可以通过关系去直接访问或用作基本的查询:

// 获取直接分配给用户的所有的权限
$permissions = $user->permissions;

// 返回所有用户通过赋予角色所继承的权限
$permissions = $user->getAllPermissions();

// 获取所有已定义的角色的集合
$roles = $user->getRoleNames(); // 返回一个集合

HasRoles trait 在你的模型上还增加了 role scope 能让你检索特定角色或者特定权限的用户:

$users = User::role('writer')->get(); // 返回角色是 'writer' 的用户

role scope 接收一个字符串, \Spatie\Permission\Models\Role 对象或者 \Illuminate\Support\Collection 对象。

这个 trait 还增加了 scope 让你只能获取到具有某个权限的用户。

$users = User::permission('edit articles')->get(); // 只返回有 'edit articles' 权限的用户 (继承角色得来的或者是直接分配的)

role scope 接收一个字符串, \Spatie\Permission\Models\Permission 对象或者 \Illuminate\Support\Collection 对象。

使用 "直接" 权限 (可以参考下面 roles 和 permissions 的使用)

分配权限给任何一个用户:

$user->givePermissionTo('edit articles');

// You can also give multiple permission at once
$user->givePermissionTo('edit articles', 'delete articles');

// You may also pass an array
$user->givePermissionTo(['edit articles', 'delete articles']);

撤销用户的某个权限:

$user->revokePermissionTo('edit articles');

在一次操作中撤销或者新增权限:

$user->syncPermissions(['edit articles', 'delete articles']);

你可以判断某个用户是否具有这个权限:

$user->hasPermissionTo('edit articles');

判断用户是否具有多个权限:

$user->hasAnyPermission(['edit articles', 'publish articles', 'unpublish articles']);

保存了权限后它将会注册在 Illuminate\Auth\Access\Gate 类的默认守卫中。所以你可以使用 Laravel 的默认 can 方法来判断用户是否有某个权限:

$user->can('edit articles');

通过角色使用权限

角色可以被分配给任意用户:

$user->assignRole('writer');

// You can also assign multiple roles at once
$user->assignRole('writer', 'admin');
// or as an array
$user->assignRole(['writer', 'admin']);

角色可以从一个用户身上移除:

$user->removeRole('writer');

角色也可以被同步:

// All current roles will be removed from the user and replaced by the array given
$user->syncRoles(['writer', 'admin']);

你可以判断一个用户是否包含某个角色:

$user->hasRole('writer');

你也可以判断一个用户是否包含给定角色列表中的一个:

$user->hasAnyRole(Role::all());

你也可以判断一个用户是否包含所有给定的角色:

$user->hasAllRoles(Role::all());

assignRole, hasRole, hasAnyRole, hasAllRolesremoveRole 这些函数可以接受一个字符串, 一个 \Spatie\Permission\Models\Role 对象 或者 一个 \Illuminate\Support\Collection 对象作为参数。

权限可以被分配给一个角色:

$role->givePermissionTo('edit articles');

你可以判断一个角色是否包含某个权限:

$role->hasPermissionTo('edit articles');

权限也可以从一个角色身上移除:

$role->revokePermissionTo('edit articles');

givePermissionTorevokePermissionTo 函数可以接受一个字符串或者一个 Spatie\Permission\Models\Permission 对象作为参数。

权限是从角色中自动继承的.
另外, 个人权限也可以分配给用户.
例如:

$role = Role::findByName('writer');
$role->givePermissionTo('edit articles');

$user->assignRole('writer');

$user->givePermissionTo('delete articles');

在上面的例子中,角色被赋予了编辑文章的权限,并且该角色被分配给了用户。
现在,用户可以编辑、删除文章。“删除”权限是直接分配给用户的直接权限。
当我们调用 $user->hasDirectPermission('delete articles') 它会返回 true,
false 对应的是 $user->hasDirectPermission('edit articles').

如果为应用程序中的角色和用户设置权限,并希望限制或者更改用户角色的继承权限(仅允许更改用户的直接权限),则这个方法就会非常有用。

你可以列出这些权限:

// Direct permissions
$user->getDirectPermissions() // Or $user->permissions;

// Permissions inherited from the user's roles
$user->getPermissionsViaRoles();

// All permissions which apply on the user (inherited and direct)
$user->getAllPermissions();

所有的响应都是 Spatie\Permission\Models\Permission 这个对象的集合.

如果我们按照之前的例子, 第一个响应将是一个具有 delete article 权限的集合,第二个响应会是一个具有 edit article权限的集合,而第三个响应将会包含之前二者的集合。

使用 Blade 语法

这个包还增加了 Blade 语法来验证当前登录的用户是否具有全部或某个给定的角色。

你可以通过传入第二个参数 guard 来进行检索。

Blade 语法 Role

测试一个特定的角色:

@role('writer')
    我是一个 writer!
@else
   我不是一个 writer...
@endrole

相当于

@hasrole('writer')
    我是一个 writer!
@else
   我不是一个 writer...
@endhasrole

测试传入的列表中的任一角色:

@hasanyrole($collectionOfRoles)
    我有一个或多个这里的权限
@else
    这些权限我都没有
@endhasanyrole
// or
@hasanyrole('writer|admin')
    我是一个 writer 或者 admin
@else
    我既不是 writer 也不是 admin
@endhasanyrole

测试是否具有所有角色

@hasallroles($collectionOfRoles)
    这些角色我都是
@else
    这些角色我都不是
@endhasallroles
// or
@hasallroles('writer|admin')
    我既是 writer 也是 admin
@else
    我不是 writer 也不是 admin
@endhasallroles

Blade 模板 与 Permissions 权限

本扩展没有提供权限相关的特殊的 Blade directives,而是使用 Laravel 自带的 @can directive 来校验用户权限。举个栗子:

@can('edit articles')
  //
@endcan

再举个栗子:

@if(auth()->user()->can('edit articles') && $some_other_condition)
  //
@endif

使用多个 guards

使用 Laravel 默认的 auth 配置时,以上例子的所有的方法都可以正常使用,不需要做额外的配置。
当有多个 guards 时,校验权限和角色时有点像命名空间。意味着每个 guard 都有与之用户模型相关的的角色和权限。

通过多重守卫来使用权限和角色

新增的权限和角色会默认使用系统默认的 guard (config('auth.defaults.guard')) 。新增权限和角色的时候,可以通过 model 的属性 guard_name 来指定 guard:

// Create a superadmin role for the admin users
$role = Role::create(['guard_name' => 'admin', 'name' => 'superadmin']);

// Define a `publish articles` permission for the admin users belonging to the admin guard
$permission = Permission::create(['guard_name' => 'admin', 'name' => 'publish articles']);

// Define a *different* `publish articles` permission for the regular users belonging to the web guard
$permission = Permission::create(['guard_name' => 'web', 'name' => 'publish articles']);

校验用户是否有指定 guard 的权限:

$user->hasPermissionTo('publish articles', 'admin');

用户授权和角色赋予

你可以使用上文所述的方法 using permissions via roles 给用户授权和赋予角色。仅需要确保权限或者角色的 guard_name 与用户的 guard 是一样的,否则将会抛出一个GuardDoesNotMatch 异常

blade directives 中使用 multiple guards

所有的 blade directivesusing blade directives 对于多个 guards都是同样适用,只需要将 guard_name 作为第二个参数即可。举个栗子:

@role('super-admin', 'admin')
    I am a super-admin!
@else
    I am not a super-admin...
@endrole

使用中间件

这个包包含 RoleMiddlewarePermissionMiddleware 俩个中间件。 你可以把他们添加到 app/Http/Kernel.php 文件中。

protected $routeMiddleware = [
    // ...
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
];

你可以使用中间件保护你的路由:

Route::group(['middleware' => ['role:super-admin']], function () {
    //
});

Route::group(['middleware' => ['permission:publish articles']], function () {
    //
});

Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () {
    //
});

另外,你可以通过 | (pipe) 字符把多个角色或者权限区分开:

Route::group(['middleware' => ['role:super-admin|writer']], function () {
    //
});

Route::group(['middleware' => ['permission:publish articles|edit articles']], function () {
    //
});

同样, 你也可以通过在构造函数中设置需要的中间件的方式保护你的控制器:

public function __construct()
{
    $this->middleware(['role:super-admin','permission:publish articles|edit articles']);
}

捕获角色和权限失败

如果你想重写默认的 403 响应, 你可以通过应用的异常捕获机制捕获 UnauthorizedException 异常:

public function render($request, Exception $exception)
{
    if ($exception instanceof \Spatie\Permission\Exceptions\UnauthorizedException) {
        // Code here ...
    }

    return parent::render($request, $exception);
}

使用 artisan 命令

你可以通过控制台使用 artisan 命令创建角色和权限。

php artisan permission:create-role writer
php artisan permission:create-permission "edit articles"

当你为特定的守卫创建角色和权限时,你可以将守卫名字作为第二个参数:

php artisan permission:create-role writer web
php artisan permission:create-permission "edit articles" web

单元测试

在你应用的测试中,如果你没有在 setUp() 测试方法中填充角色和权限的数据作为测试的一部分,你很可能会遇到先有鸡还是先有蛋的问题,角色和权限还没注册到门脸上 (因为你测试它们是在门脸注册之后的),解决这个问题很简单:只需要像以下这样在你的测试里面的添加 setUp() 方法来重新注册权限:

    public function setUp()
    {
        // 首先像正常一样调用以下父类的setUp()方法
        parent::setUp();

        // 现在重新注册角色和权限
        $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();
    }

数据库填充

关于数据库填充有两点需要注意

  1. 最好在填充数据之前更新以下 spatie.permission.cache ,以避免缓存冲突的错误,这可以用一条 Artisan 命令解决 (可以参考后面故障排除:缓存部分) 或者直接在一个 seeder 类中清除缓存 (可以看看下面的例子)。
  2. 这里是一个数据填充示例,它先清除缓存,再创建权限,然后为角色分配权限:

    use Illuminate\Database\Seeder;
    use Spatie\Permission\Models\Role;
    use Spatie\Permission\Models\Permission;
    
    class RolesAndPermissionsSeeder extends Seeder
    {
        public function run()
        {
            // 重置角色和权限的缓存
            app()['cache']->forget('spatie.permission.cache');
    
            // 创建权限
            Permission::create(['name' => 'edit articles']);
            Permission::create(['name' => 'delete articles']);
            Permission::create(['name' => 'publish articles']);
            Permission::create(['name' => 'unpublish articles']);
    
            // 创建角色并赋予已创建的权限
            $role = Role::create(['name' => 'writer']);
            $role->givePermissionTo('edit articles');
            $role->givePermissionTo('delete articles');
    
            $role = Role::create(['name' => 'admin']);
            $role->givePermissionTo('publish articles');
            $role->givePermissionTo('unpublish articles');
        }
    }
    

扩展

如果你想扩展现有的 Role 或者 Permission 模型,请注意:

  • 你的 Role 模型需要继承
  • Spatie\Permission\Models\Role 模型
  • 你的 Permission 模型需要继承 Spatie\Permission\Models\Permission 模型

如果你需要替换现有的 Role 或者 Permission 模型,请记住:

  • 你的 Role 模型需要实现
  • Spatie\Permission\Contracts\Role
  • 你的 Permission 需要实现 Spatie\Permission\Contracts\Permission

在这两种情况下, 无论是扩展还是替换, 你需要在配置中指明你的新模型。 为此,你必须在发布配置后,更新在配置文件中的 models.rolemodels.permission 这两个值。通过如下命令:

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

缓存

角色和权限数据被存在缓存中以加快性能。

当您使用提供的方法来操作角色和权限时,缓存会自动重置:

$user->assignRole('writer');
$user->removeRole('writer');
$user->syncRoles(params);
$role->givePermissionTo('edit articles');
$role->revokePermissionTo('edit articles');
$role->syncPermissions(params);
$permission->assignRole('writer');
$permission->removeRole('writer');
$permission->syncRoles(params);

然而,如果你直接通过数据库操作权限、角色数据,而不是调用我们提供的方法,应用程序不会出现你做的更改,除非你手动重置缓存。

手动重置缓存

如需手动重置此扩展包的缓存,运行命令:

php artisan cache:forget spatie.permission.cache

缓存标识符

建议:如果你正在使用诸如 redismemcached 等缓存服务,或者还有其他的网站在你的服务器上运行,你可能会遇到缓存冲突,那么设置自己的缓存前缀是深谋远虑的。
/ config / cache.php 文件中为每个应用程序设置独有的标识符。 这将阻止其他应用程序意外使用或更改您的缓存数据。

需要用户界面?

这个扩展包没有用户界面,你需要自己创建。 请参考 Caleb Oki这篇教程

测试

composer test

更新日志

请参阅 更新日志 以了解更多最近更新的信息

贡献代码

详情请参阅 贡献代码

安全

如果您发现任何与安全相关的问题, 请发送邮件至 freek@spatie.be,而不是使用问题跟踪器。

明信片

你可以免费使用此扩展包,但如果你在生产环境中使用,我们将非常感谢你给我们发送一张明信片,告诉我们正在使用哪一个扩展包。

我们的地址是: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium.

我们把所有收到的明信片发布到了公司官网

鸣谢

这个扩展包很大程度上基于 Jeffrey Way 非常酷的 Laracasts
关于 权限和角色的课程。这是他的源码地址 in this repo on GitHub.

特别要感谢 Alex Vanderbist ,他对v2版本有过很大帮助, 还有 Chris Brown ,他长期帮助我们维护着这个扩展包。

资源

选择

Povilas Korop 写的文章 Laravel新闻的一篇文章里做了对比。他用laravel-permission和Joseph SilberBouncer)对比, 在我们的书里也是一个优秀的包。

支持我们

Spatie是一个web设计机构,在比利时的安特卫普。你可以看到我们的开源项目的概览 在我们的网站上

你的业务依赖于我们的贡献吗? 在Patreon资助我们。这些钱将用于维护老产品,开发新产品。

许可

麻省理工学院许可证 (MIT). 有关更多信息,请参阅 许可证文件


summerblue
11k 声望15.4k 粉丝

刻意练习,每日精进